/**************************************************************************//**
 * @file     rcc.c
 * @brief    stm32f1xx rcc 模块操作接口
 * @version  V0.0.1
 * @date     2020-02-01
 ******************************************************************************/
/*
 * Copyright (c) 2020, 2020 by xiao xiang. All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software
 * is freely granted, provided that this notice is preserved.
 */
#include "common.h"
#include "config_api.h"
#include "hal_stm32f1xx_uart_api.h"
#include "hal_stm32f1xx_rcc_api.h"
#include "hal_stm32f1xx_flash_api.h"

#include "rcc.h"

// 系统时钟频率，默认启动是8MHz，重新配置RCC后需要更新该全局变量
uint32_t SystemCoreClock = HAL_HCLK_FREQ_8M;

const uint8_t g_aAHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};    // AHB分频系数表
const uint8_t g_aAPBPrescTable[8] =  {0, 0, 0, 0, 1, 2, 3, 4};    // APB总线分频系数表
const uint8_t g_aPLLMULFactorTable[16] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16};    // PLL倍频系数表
const uint8_t g_aPredivFactorTable[2] = {1, 2};   // HSE作为PLL时钟来源时的HSE分频系数

// 计算获取当前的SYSCLK频率
uint32_t HAL_RccGetSysclk(void)
{
    volatile UN_RCC_CFGR unCfgr;
    uint32_t prediv;
    uint32_t pllclk;
    uint32_t pllmul;
    uint32_t sysclockfreq;

    unCfgr.reg = RCC->CFGR.reg;

    /* Get SYSCLK source -------------------------------------------------------*/
    switch (unCfgr.SWS)
    {
    case RCC_CFGR_SW_HSE: /* HSE作为SYSCLK的时钟来源 */
        sysclockfreq = CFG_HSE_FREQ;
        break;
    case RCC_CFGR_SW_PLL: /* PLL作为SYSCLK的时钟来源 */
        pllmul = g_aPLLMULFactorTable[unCfgr.PLLMUL];   // 获取PLL的倍频系数
        if ((unCfgr.PLLSRC) == RCC_CFGR_PLLSRC_HSE) {
            // HSE作为PLL时钟来源，继续确认HSE是否有二分频
            prediv = g_aPredivFactorTable[unCfgr.PLLXTPRE];

            // 最终SYSCLK频率 = 分频后的HSE频率 * PLL倍频系数
            pllclk = (uint32_t)((CFG_HSE_FREQ * pllmul) / prediv);
        } else {
            // HSI作为PLL时钟来源，SYSCLK = (HSI频率 / 2) * PLL倍频系数
            pllclk = (uint32_t)((HAL_HSI_FREQ >> 1) * pllmul);
        }
        sysclockfreq = pllclk;
        break;
    case RCC_CFGR_SW_HSI: /* HSI作为SYSCLK的时钟来源 */
    default:
        sysclockfreq = HAL_HSI_FREQ;
        break;
    }

    return sysclockfreq;
}

// 通过计算获取当前的HCLK频率，由SYSCLK分频而来
uint32_t HAL_RccGetHclk(void)
{
    // 系统时钟 = SYSCLK / HCKL分频系数
    return (HAL_RccGetSysclk() >> g_aAHBPrescTable[RCC->CFGR.HPRE]);
}

// 通过计算获取当前的PCLK2频率，由HCLK分频而来
uint32_t HAL_RccGetPclk2(void)
{
    return (HAL_RccGetHclk() >> g_aAPBPrescTable[RCC->CFGR.PPRE2]);
}

// 通过计算获取当前的PCLK1频率，由HCLK分频而来
uint32_t HAL_RccGetPclk1(void)
{
    return (HAL_RccGetHclk() >> g_aAPBPrescTable[RCC->CFGR.PPRE1]);
}

// 返回当前系统时钟频率
uint32_t HAL_RccSysclkFreqGet(void)
{
    return  SystemCoreClock;
}

// 更新系统时钟频率
void RccSysclkUpdate(uint32_t freq)
{
    SystemCoreClock = freq;
    return;
}

/**
  \brief   HSE使能或禁止函数
  \details 如果是使能HSE，则同时会设置HSE不分频
  \param [in]   isEnable  FALSE=禁止HSE；TRUE=HSE使能
  \return  RET_OK       操作成功
  \return  RET_TIMEOUT  操作失败
  \note    
 */
uint32_t RccHseCtrl(uint8_t isEnable)
{
    uint32_t timeout = RCC_CLK_WAIT_TIMEOUT;
    uint32_t HseState;      // 设置成功后期望HSERDY的值

    if (isEnable == TRUE) {
        RCC->CFGR.PLLXTPRE = RCC_CFGR_PLLXTPRE_HSE1;   // 配置HSE不分频
        RCC->CR.HSEON = ENABLE;     // 使能HSE  
        HseState = ENABLE;
    } else {
        RCC->CR.HSEON = DISABLE;    // 禁止HSE
        HseState = DISABLE;
    }

    while (timeout--) {
        // HSERDY为期望的值之后返回成功
        if (RCC->CR.HSERDY == HseState) {
            return RET_OK;
        }
    }

    return RET_TIMEOUT;
}

/**
  \brief   PLL使能或禁止函数
  \details 如果是使能HSE，则同时会设置HSE不分频
  \param [in]   isEnable  FALSE=禁止PLL；TRUE=使能PLL
  \return  RET_OK       操作成功
  \return  RET_TIMEOUT  操作失败
  \note    
 */
uint32_t RccPllCtrl(uint8_t isEnable)
{
    uint32_t timeout = RCC_CLK_WAIT_TIMEOUT;
    uint32_t pllState;      // 设置后期望PLLRDY的值

    if (isEnable == TRUE) {
        RCC->CR.PLLON = ENABLE;
        pllState = ENABLE;
    } else {
        RCC->CR.PLLON = DISABLE;
        pllState = DISABLE;
    }

    while (timeout--) {
        // HSERDY为期望的值之后返回成功
        if (RCC->CR.HSERDY == pllState) {
            return RET_OK;
        }
    }

    return RET_ERROR;
}

/**
  \brief    使能PLL，并将其作为Sysclk的来源
  \details  使能前会将PLL设为九倍频，如果是HSE作为时钟来源并且是8M，则调整后sysclk=72M
  \param    无
  \return   RET_OK       操作成功
  \return   RET_ERROR    操作失败
  \note     如果当前PLL已经作为了系统时钟来源，则不允许再进行调整
 */
uint32_t RccPllActive(void)
{
    // 如果当前系统时钟来源已经是PLL，则直接返回错误
    if (RCC->CFGR.SWS == RCC_CFGR_SW_PLL) {
        USART_SyncTransmit(USART_IDX1, "RCC already PLL valid\r\n");
        return RET_ERROR;
    }

    // 如果PLL没有作为系统时钟源，但已经使能，则需要先关闭PLL才能进行配置
    if (RCC->CR.PLLON == ENABLE) {
        USART_SyncTransmit(USART_IDX1, "RCC disable PLL first\r\n");
        if (RccPllCtrl(FALSE) != RET_OK) {
            USART_SyncTransmit(USART_IDX1, "RCC disable PLL failed\r\n");
            return RET_ERROR;
        }
    }

    // 配置PLL来源是HSE，同时PLL倍频设为九倍频
    RCC->CFGR.PLLMUL = RCC_CFGR_PLLMUL_DIV9;
    RCC->CFGR.PLLSRC = RCC_CFGR_PLLSRC_HSE;

    // 使能PLL
    if (RccPllCtrl(TRUE) != RET_OK) {
        USART_SyncTransmit(USART_IDX1, "RCC enable PLL failed\r\n");
        return RET_ERROR;
    }

    return RET_OK;
}

// 将SYSCLK的时钟来源切换为PLL
uint32_t RccSysclkSetSrcPll(void)
{
    uint32_t timeout = RCC_CLK_WAIT_TIMEOUT;

    RCC->CFGR.SW = RCC_CFGR_SW_PLL;     // 系统时钟来源切换为PLL
    while (timeout--) {
        if (RCC->CFGR.SWS == RCC_CFGR_SW_PLL) {
            return RET_OK;
        }
    }

    return RET_ERROR;
}

// 配置HCLK、SYSCLK、PCLK1、PCLK2、FLASH LATENCY时钟
uint32_t RccClockConfig(void)
{
    // 配置Flash ACR LATENCY，提频前需要先设置LATENCY再提频，降频则需要先降频再设置LATENCY
    FLASH->ACR.LATENCY = FLASH_ACR_LATENCY_2;

    // 配置HCLK，AHB不分频，频率和SYSCLK保持一致
    RCC->CFGR.HPRE = RCC_CFGR_HPRE_DIV1;

    // 配置SYSCLK，时钟来源切换成PLL，完成后HCLK也提频了
    if (RccSysclkSetSrcPll() != RET_OK) {
        return RET_ERROR;
    }

    // 配置PCLK1
    RCC->CFGR.PPRE1 = RCC_CFGR_PPRE1_DIV2;

    // 配置PCLK2
    RCC->CFGR.PPRE1 = RCC_CFGR_PPRE1_DIV1;

    return RET_OK;
}

/**
  * @brief  使用外部晶振8M，并且系统时钟频率设为72M，完成后里面会更新系统时钟频率变量
  * @note   函数内部会调整晶振和PLL设置，并配置AHB/APB总线时钟频率
  * @note   当系统被复位，或从STOP或STANDBY模式中恢复时，默认采用HSI作为系统时钟，
  *         因此需要重新设置
  * @retval RET_OK：设置成功    RET_ERROR:设置失败
  */
uint32_t HAL_RccSwitchHse8mClk72m(void)
{
    // 使能外部8M晶振
    if (RccHseCtrl(TRUE) != RET_OK) {
        return RET_ERROR;
    }

    // 使能PLL，将外部HSE作为时钟源，并倍频到72M
    if (RccPllActive() != RET_OK) {
        return RET_ERROR;
    }

    // 将PLL作为系统时钟来源，一旦配置成功，影响PLL的所有配置就不允许再修改,
    // 比如HSI是否二分频、HSE作为PLL时钟来源、PLL倍频系数等。
    // 同时一并配置AHB、APB等总线时钟
    if (RccClockConfig() != RET_OK) {
        return RET_ERROR;
    }
    
    // 成功后刷新系统时钟频率变量
    RccSysclkUpdate(HAL_HCLK_FREQ_72M);

    return RET_OK;
}
